package com.sanweibook.jhook.dal.reload;

import com.google.common.collect.Lists;
import com.sanweibook.jhook.dal.reload.exception.MapperReloadException;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;


/**
 * Created by twg on 2017/6/18.
 */
public class MapperReLoadProcessor implements InitializingBean {
    private static final Logger log = LoggerFactory.getLogger( MapperReLoadProcessor.class );

    /*mapper 文件编译后的存放路径*/
    private Resource[] mapperLocations;
    private SqlSessionFactory sqlSessionFactory;

    /*定时刷新mapper的间隔时间*/
    private long reloadMillis;
    /*是否开启定时加载mapper*/
    private boolean isReload;
    /*mapper 文件实际存放路径*/
    private String mapperPath;

    private final Properties properties = new Properties();

    private void init() throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = this.getClass().getClassLoader().getResourceAsStream( "jdbc.properties" );
            properties.load( inputStream );
            mapperPath = properties.getProperty( "mybatis.mapper.path" );
            isReload = Boolean.parseBoolean( properties.getProperty( "mybatis.isReload" ) );
            reloadMillis = Long.getLong( properties.getProperty( "mybatis.reload.millis" ), 20000L );
            if (isReload) {
                Executor executor = Executors.newScheduledThreadPool( 1 );
                executor.execute( new MybatisMapperReload() );
            }
        } catch (IOException e) {
            log.error( "mapper reload processor is error。 ", e );
            throw new MapperReloadException( "mapper reload process load jdbc.properties is error." );
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }

    /**
     * 定时刷新，获取需要重新加载的mapper
     *
     * @param lastReloadTime 上次加载时间
     * @return
     * @throws IOException
     */
    private List<File> filesOfReLoad(final long lastReloadTime) throws IOException, URISyntaxException {
        List<File> files = Lists.newArrayList();
        Resource[] resources = getMapperLocations();
        List<String> filePaths = Lists.newArrayList();
        for (Resource resource : resources) {
            StringBuilder sb = new StringBuilder( ResourceUtils.FILE_URL_PREFIX + mapperPath );
            if (resource instanceof UrlResource) {
                UrlResource urlResource = (UrlResource) resource;
                String filePath = urlResource.getURL().getPath();
                filePath = filePath.substring( filePath.lastIndexOf( ResourceUtils.JAR_URL_SEPARATOR ) + 1 );
                filePaths.add( sb.append( filePath ).toString() );
            } else if (resource instanceof FileSystemResource) {
                FileSystemResource fileSystemResource = (FileSystemResource) resource;
                String filePath = fileSystemResource.getFile().getPath();
                filePath = filePath.replace( "target/classes", "src/main/resources" );
                sb = new StringBuilder( ResourceUtils.FILE_URL_PREFIX + filePath );
                filePaths.add( sb.toString() );
            }
        }
        for (String filePath : filePaths) {
            File file = new File( new URI( filePath ) );
            if (file.lastModified() > lastReloadTime) {
                files.add( file );
            }
        }
        return files;
    }

    private final class MybatisMapperReload implements Runnable {
        private long lastReloadTime = System.currentTimeMillis();

        @Override
        public void run() {
            log.debug( "======== reload mapper is starting ========" );
            try {
                Thread.sleep( reloadMillis );
            } catch (InterruptedException e) {
                log.error( "The Thread {} is Interrupted.", Thread.currentThread().getName(), e );
                Thread.currentThread().interrupt();
            }
            List<File> files = null;
            try {
                files = filesOfReLoad( lastReloadTime );
            } catch (IOException | URISyntaxException e) {
                log.error( "read modified files form  mapperLocations is error.", e );
            }
            for (File file : files) {
                lastReloadTime = System.currentTimeMillis();
                if (log.isDebugEnabled()) {
                    log.debug( "reload mapper file name is {}", file.getName() );
                }
                try (FileInputStream fileInputStream = new FileInputStream( file )) {
                    reloadMapper( fileInputStream, file.getAbsolutePath() );
                } catch (IOException e) {
                    log.error( "the file name {} not found.", file.getName(),e );
                    throw new MapperReloadException( e.getMessage() );
                }
            }

            int count = 0;
            while (true) {
                count++;
                if (count > Integer.MAX_VALUE) {
                    break;
                }
                this.run();
            }
        }
    }

    private void reloadMapper(InputStream inputStream, String resource) {
        Configuration configuration = (Configuration) ((DefaultSqlSessionFactory) getSqlSessionFactory()).getConfiguration();
        if (configuration.isResourceLoaded( resource )) {
            configuration.removeLoadedResource( resource );
        }
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder( inputStream, configuration, resource, configuration.getSqlFragments() );
        xmlMapperBuilder.parse();
    }

    public Resource[] getMapperLocations() {
        return mapperLocations;
    }

    public void setMapperLocations(Resource[] mapperLocations) {
        this.mapperLocations = mapperLocations;
    }

    public SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
}
